package com.scope.tasks;

import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.Log;
import android.view.View;
import android.view.animation.DecelerateInterpolator;

/**
 * Created by Samrith on 11/2/13.
 */
public class ZoomAnimator {
    public ZoomAnimator(Context context) {
        mShortAnimationDuration = context.getResources().getInteger(android.R.integer.config_shortAnimTime);
        mCalculated = false;
        mRevCalculated = false;
    }

    protected android.animation.Animator mCurrentAnimator;
    protected int mShortAnimationDuration;

    protected View mStartView;
    protected ViewState mStartViewState;

    public void setStartView(final View view, ViewState state) {
        mStartView = view;
        mStartViewState = state;
        mCalculated = false;
    }

    protected View mMainView;
    protected ViewState mMainViewState;

    public void setMainView(final View view, ViewState state) {
        mMainView = view;
        mMainViewState = state;
        mCalculated = false;
        mRevCalculated = false;
    }

    protected View mEndView;
    protected ViewState mEndViewState;

    public void setEndView(final View view, ViewState state) {
        mEndView = view;
        mEndViewState = state;
        mRevCalculated = false;
    }

    protected View mFrame;

    public void setFrame(final View frame) {
        mFrame = frame;
        mCalculated = false;
    }

    protected Rect mStartBounds;
    protected Rect mMainBounds;
    protected Rect mEndBounds;
    protected float mStartScale;
    protected float mEndScale;

    public void animate() {
        if (mCurrentAnimator != null) {
            mCurrentAnimator.cancel();
        }

        if (!mCalculated) {
            try {
                calculateAnimationProperties();
            } catch (Exception e) {
                Log.i(this.toString(), e.toString());
                return;
            }
        }

        setStartAnimateViewState();

        AnimatorSet set = new AnimatorSet();
        setAnimator(set);
        set.setDuration(mShortAnimationDuration);
        set.setInterpolator(new DecelerateInterpolator());
        set.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(android.animation.Animator animation) {
                setEndAnimateViewState();
                mCurrentAnimator = null;
            }

            @Override
            public void onAnimationCancel(android.animation.Animator animation) {
                setEndAnimateViewState();
                mCurrentAnimator = null;
            }
        });
        set.start();
        mCurrentAnimator = set;
    }

    protected void setAnimator(AnimatorSet set) {
        // Construct and run the parallel animation of the four translation and scale properties
        // (X, Y, SCALE_X, and SCALE_Y).
        set
                .play(ObjectAnimator.ofFloat(mMainView, View.X, mStartBounds.left, mMainBounds.left))
                .with(ObjectAnimator.ofFloat(mMainView, View.Y, mStartBounds.top, mMainBounds.top))
                .with(ObjectAnimator.ofFloat(mMainView, View.SCALE_X, mStartScale, 1f))
                .with(ObjectAnimator.ofFloat(mMainView, View.SCALE_Y, mStartScale, 1f));
    }

    public void animateRev() {
        if (mCurrentAnimator != null) {
            mCurrentAnimator.cancel();
        }

        if (!mRevCalculated) {
            try {
                calculateRevAnimationProperties();
            } catch (Exception e) {
                Log.i(this.toString(), e.toString());
                return;
            }
        }

        setStartAnimateViewRevState();

        AnimatorSet set = new AnimatorSet();
        setAnimatorRev(set);
        set.setDuration(mShortAnimationDuration);
        set.setInterpolator(new DecelerateInterpolator());
        set.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(android.animation.Animator animation) {
                setEndAnimateViewRevState();
                mCurrentAnimator = null;
            }

            @Override
            public void onAnimationCancel(android.animation.Animator animation) {
                setEndAnimateViewRevState();
                mCurrentAnimator = null;
            }
        });
        set.start();
        mCurrentAnimator = set;
    }

    protected void setAnimatorRev(AnimatorSet set) {
        // Animate the four positioning/sizing properties in parallel, back to their
        // original values.
        set
                .play(ObjectAnimator.ofFloat(mMainView, View.X, mEndBounds.left))
                .with(ObjectAnimator.ofFloat(mMainView, View.Y, mEndBounds.top))
                .with(ObjectAnimator.ofFloat(mMainView, View.SCALE_X, mEndScale))
                .with(ObjectAnimator.ofFloat(mMainView, View.SCALE_Y, mEndScale));
    }

    protected boolean mCalculated;
    protected boolean mRevCalculated;

    protected void calculateAnimationProperties() {
        mStartBounds = new Rect();
        mMainBounds = new Rect();
        mStartScale = calculateAnimationProperties(mStartView, mMainView, mStartBounds, mMainBounds);
        mCalculated = true;
    }

    protected void calculateRevAnimationProperties() {
        mEndBounds = new Rect();
        mMainBounds = new Rect();
        mEndScale = calculateAnimationProperties(mEndView, mMainView, mEndBounds, mMainBounds);
        mRevCalculated = true;
    }

    protected float calculateAnimationProperties(View startView, View mainView, Rect startBounds, Rect mainBounds) {
        // Calculate the starting and ending bounds for the zoomed-in image. This step
        // involves lots of math. Yay, math.
        final Point globalOffset = new Point();

        // The start bounds are the global visible rectangle of the thumbnail, and the
        // final bounds are the global visible rectangle of the container view. Also
        // set the container view's offset as the origin for the bounds, since that's
        // the origin for the positioning animation properties (X, Y).
        startView.getGlobalVisibleRect(startBounds);
        mFrame.getGlobalVisibleRect(mainBounds, globalOffset);
        startBounds.offset(-globalOffset.x, -globalOffset.y);
        mainBounds.offset(-globalOffset.x, -globalOffset.y);
        float startScale = extendStartBounds(startBounds, mainBounds);

        setPivotTopLeft(startView);
        setPivotTopLeft(mainView);

        return startScale;
    }

    protected void setPivotTopLeft(View view) {
        view.setPivotX(0f);
        view.setPivotY(0f);
    }

    // Adjust the start bounds to be the same aspect ratio as the final bounds using the
    // "center crop" technique. This prevents undesirable stretching during the animation.
    // Return the calculated the start scaling factor (the end scaling factor is always 1.0).
    protected float extendStartBounds(Rect startBounds, Rect endBounds) {
        float startScale;

        if ((float) endBounds.width() / endBounds.height()
                > (float) startBounds.width() / startBounds.height()) {
            // Extend start bounds horizontally
            startScale = (float) startBounds.height() / endBounds.height();
            float startWidth = startScale * endBounds.width();
            float deltaWidth = (startWidth - startBounds.width()) / 2;
            startBounds.left -= deltaWidth;
            startBounds.right += deltaWidth;
        } else {
            // Extend start bounds vertically
            startScale = (float) startBounds.width() / endBounds.width();
            float startHeight = startScale * endBounds.height();
            float deltaHeight = (startHeight - startBounds.height()) / 2;
            startBounds.top -= deltaHeight;
            startBounds.bottom += deltaHeight;
        }

        return startScale;
    }

    protected void setStartAnimateViewState() {
        setStartAnimateViewState(mMainView, mMainViewState);
    }

    protected void setEndAnimateViewState() {
    }

    protected void setStartAnimateViewState(View view, ViewState state) {
        if (state == ViewState.FIX) {
            view.setAlpha(FIX_MIN_ALPHA);
        } else if (state == ViewState.GONE) {
            view.setVisibility(View.VISIBLE);
        } else if (state == ViewState.VISIBLE) {
            view.setVisibility(View.GONE);
        } else {
        }
    }

    protected void setStartAnimateViewRevState() {
    }

    protected void setEndAnimateViewRevState() {
        setEndAnimateViewState(mMainView, mMainViewState);
    }

    protected void setEndAnimateViewState(View view, ViewState state) {
        if (state == ViewState.FIX) {
            view.setAlpha(FIX_MAX_ALPHA);
        } else if (state == ViewState.GONE) {
            view.setVisibility(View.GONE);
        } else if (state == ViewState.VISIBLE) {
            view.setVisibility(View.VISIBLE);
        } else {
        }
    }

    public enum ViewState {FIX, GONE, VISIBLE}

    protected final static float FIX_MIN_ALPHA = 0.3f;
    protected final static float FIX_MAX_ALPHA = 1f;
}
